import ASingleton from "../Abstract/ASingleton";


// TODO: YaMD5 需要导入
declare let YaMD5: any;
export class TaskData {
    //private mRequestURL : string = null;  // Key
    constructor(storagePath: string, md5: string, succeedCallback: Function, progressCallback?: Function, errorCallback?: Function, retry?: boolean) {
        this.StoragePath = storagePath;
        this.OnSucceedCallback = succeedCallback;
        this.OnProgressCallback = progressCallback;
        this.OnErrorCallback = errorCallback;
        this.Md5 = md5;
        this.Retry = retry;
        this.BInDownLoadState = false;
    }

    BInDownLoadState: boolean = false;
    StoragePath: string = null;
    OnSucceedCallback: Function = null;
    OnProgressCallback: Function = null;
    OnErrorCallback: Function = null;
    Md5: string = '';
    Retry: boolean = false;

    ByteSize: number = -1;
    SaveSize: number = -1;
}

const { ccclass, property } = cc._decorator;

@ccclass
export default class Downloader extends ASingleton {
    @property({ type: cc.Integer, tooltip: "重试间隔时间" })
    private RetryInterval: number = 1;
    // @ts-ignore
    private mDownloader: jsb.Downloader = null;
    private mTasks: Map<string, TaskData> = new Map<string, TaskData>();

    public GetDownLoadTasks() { return this.mTasks }

    protected Load() {
        if (cc.sys.isNative) {
            this.mDownloader = new jsb.Downloader();
            this.mDownloader.setOnFileTaskSuccess(this.OnSucceed.bind(this));
            this.mDownloader.setOnTaskProgress(this.OnProgress.bind(this));
            this.mDownloader.setOnTaskError(this.OnError.bind(this));
        }
    }

    protected Destroy() {

    }

    private OnSucceed(task) {
        //   std::string identifier;
        //   std::string requestURL;
        //   std::string storagePath;
        let data = this.mTasks.get(task.requestURL);
        data.BInDownLoadState = false;
        data.OnSucceedCallback && data.OnSucceedCallback(task.storagePath, "CheckMd5");
        if (this.CheckMd5(jsb.fileUtils.getDataFromFile(data.StoragePath), data.Md5)) {
            this.mTasks.delete(task.requestURL);
            data.OnSucceedCallback && data.OnSucceedCallback(task.storagePath, "Succeed");
        }
        else {
            this.OnError(task, -99, -99, 'md5 fail');
        }
    }

    // bytesReceived 剩余未收，totalBytesReceived 已收到，totalBytesExpected 总字节数
    private OnProgress(task, bytesReceived, totalBytesReceived, totalBytesExpected) {
        try {
            let data = this.mTasks.get(task.requestURL);
            if (data.ByteSize === -1) {
                data.ByteSize = totalBytesExpected;
            }
            data.SaveSize = totalBytesReceived;
            let cb = data.OnProgressCallback;
            if (cb) {
                cb(task.storagePath, bytesReceived, data.SaveSize, data.ByteSize);
            }
            this.mTasks.get(task.requestURL).BInDownLoadState = true;
        }
        catch (e) {
            console.log(e);
        }
    }

    private OnError(task, errorCode, errorCodeInternal, errorStr) {
        try {
            let cb = this.mTasks.get(task.requestURL).OnErrorCallback;
            if (cb) {
                cb(task.storagePath, errorCode, errorCodeInternal, errorStr);
            }
            this.mTasks.get(task.requestURL).BInDownLoadState = false;
        }
        catch (e) {
            console.log(e);
        }

        let data = this.mTasks.get(task.requestURL);
        if (data.Retry) {
            let self = this;
            this.scheduleOnce(() => {
                self.mDownloader.createDownloadFileTask(task.requestURL, data.StoragePath);
            }, self.RetryInterval);
        }
    }

    private CheckMd5(obj: any, md5Str: string) {
        if (md5Str == '-1') {
            return true;
        }

        let md5 = new YaMD5();
        md5.start();
        md5.appendByteArray(obj);
        return md5.end() == md5Str.toLowerCase();
    }

    public FindTask(url: string): TaskData {
        if (this.mTasks.has(url)) {
            return this.mTasks.get(url);
        }
        return null;
    }

    public RemoveCallback(url: string) {
        if (!this.mTasks.has(url)) { return; }
        try {
            let data = this.mTasks.get(url);
            data.OnProgressCallback = null;
            data.OnErrorCallback = null;
            data.OnSucceedCallback = null;
        }
        catch (e) {
            console.log(e);
        }
    }

    public RefreshCallback(url: string, onSucceed: Function, onError: Function, onProgress: Function) {
        if (!this.mTasks.has(url)) { return; }
        try {
            let data = this.mTasks.get(url);
            data.OnProgressCallback = onProgress;
            data.OnErrorCallback = onError;
            data.OnSucceedCallback = onSucceed;
        }
        catch (e) {
            console.log(e);
        }
    }

    public Download(url: string, fullName: string, md5: string, onSucceed: Function, retry?: boolean, onError?: Function, onProgress?: Function) {
        if (this.mTasks.has(url)) {
            this.RefreshCallback(url, onSucceed, onError, onProgress);
            return;
        }

        this.mTasks.set(url, new TaskData(fullName, md5, onSucceed, onProgress, onError, retry));
        if (cc.sys.isNative) {
            this.mDownloader.createDownloadFileTask(url, fullName);
        }
        else {
            let self = this;
            let xhr = new XMLHttpRequest();
            xhr.responseType = "arraybuffer";
            xhr.addEventListener("progress", function (evt) {
                let data = self.mTasks.get(url);
                try {
                    data.OnProgressCallback(data.StoragePath, evt.total - evt.loaded, evt.loaded, evt.total);
                }
                catch (e) {
                    console.log(e);
                }
            });

            xhr.addEventListener("error", function (evt) {
                let data = self.mTasks.get(url);
                try {
                    if (data.OnErrorCallback) {
                        data.OnErrorCallback(data.StoragePath, xhr.readyState, xhr.status, "error");
                    }
                }
                catch (e) {
                    console.log(e);
                }
            });

            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status < 400)) {
                    let data = self.mTasks.get(url);

                    if (self.CheckMd5(xhr.response, data.Md5)) {
                        //TODO : Save
                        try {
                            data.OnSucceedCallback(data.StoragePath);
                        }
                        catch (e) {
                            console.log(e);
                        }
                    }
                    else {
                        if (data.Retry) {
                            try {
                                if (data.OnErrorCallback) {
                                    data.OnErrorCallback(data.StoragePath, -99, -99, 'md5 fail TODO::HttpDownload')
                                }
                            }
                            catch (e) {
                                console.log(e);
                            }
                        }
                        else {
                            try {
                                if (data.OnErrorCallback) {
                                    data.OnErrorCallback(data.StoragePath, -99, -99, 'md5 fail')
                                }
                            }
                            catch (e) {
                                console.log(e);
                            }
                        }
                    }
                }
                else {
                    let data = self.mTasks.get(url);
                    try {
                        data.OnSucceedCallback(data.StoragePath, xhr.readyState, xhr.status, xhr.statusText);//网络连接失败
                    }
                    catch (e) {
                        console.log(e);
                    }
                }
            }
            xhr.open("GET", url, true);
            xhr.send();
        }
    }


    protected static mInstance: Downloader = null;
    public static get obj(): Downloader {
        if (Downloader.mInstance == null) {
            console.log("[Downloader.obj] inital game first.");
        }
        return Downloader.mInstance;
    }
}